package com.app.util.common;

/**
 * Copyright (c) 2011 IETF Trust and the persons identified as
 * authors of the code. All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted pursuant to, and subject to the license
 * terms contained in, the Simplified BSD License set forth in Section
 * 4.c of the IETF Trust’s Legal Provisions Relating to IETF Documents
 * (http://trustee.ietf.org/license-info).
 */

import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import java.math.BigInteger;
import java.util.TimeZone;

import java.util.Calendar;

/**
 * This is an example implementation of the OATH TOTP algorithm. Visit www.openauthentication.org for more information.
 *
 * @author Johan Rydell, PortWise, Inc.
 */
public class TOTP {
	public TOTP() {
    }

    /**
     * This method uses the JCE to provide the crypto algorithm. HMAC computes a Hashed Message Authentication Code with the crypto hash algorithm as
     * a parameter.
     *
     * @param crypto   : the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
     * @param keyBytes : the bytes to use for the HMAC key
     * @param text     : the message or text to be authenticated
     */

    private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
        try {
            Mac hmac;
            hmac = Mac.getInstance(crypto);
            SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
            hmac.init(macKey);
            return hmac.doFinal(text);
        } catch (GeneralSecurityException gse) {
            throw new UndeclaredThrowableException(gse);
        }
    }

    /**
     * This method converts a HEX string to Byte[]
     *
     * @param hex : the HEX string
     * @return: a byte array
     */
    private static byte[] hexStr2Bytes(String hex) {
        // Adding one byte to get the right conversion
        // Values starting with "0" can be converted
        byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
        // Copy all the REAL bytes, not the "first"
        byte[] ret = new byte[bArray.length - 1];
        for (int i = 0; i < ret.length; i++)
            ret[i] = bArray[i + 1];
        return ret;
    }

    private static final int[] DIGITS_POWER
            // 0 1 2 3 4 5 6 7 8
            = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};

    /**
     * This method generates a TOTP value for the given set of parameters.
     *
     * @param key          : the shared secret, HEX encoded
     * @param time         : a value that reflects a time
     * @param returnDigits : number of digits to return
     * @return: a numeric String in base 10 that includes {@link truncationDigits} digits
     */
    public static String generateTOTP(String key, String time, String returnDigits) {
        return generateTOTP(key, time, returnDigits, "HmacSHA1");
    }

    /**
     * This method generates a TOTP value for the given set of parameters.
     *
     * @param key          : the shared secret, HEX encoded
     * @param time         : a value that reflects a time
     * @param returnDigits : number of digits to return
     * @return: a numeric String in base 10 that includes {@link truncationDigits} digits
     */
    public static String generateTOTP256(String key, String time, String returnDigits) {
        return generateTOTP(key, time, returnDigits, "HmacSHA256");
    }

    /**
     * This method generates a TOTP value for the given set of parameters.
     *
     * @param key          : the shared secret, HEX encoded
     * @param time         : a value that reflects a time
     * @param returnDigits : number of digits to return
     * @return: a numeric String in base 10 that includes {@link truncationDigits} digits
     */
    public static String generateTOTP512(String key, String time, String returnDigits) {
        return generateTOTP(key, time, returnDigits, "HmacSHA512");
    }

    /**
     * This method generates a TOTP value for the given set of parameters.
     *
     * @param key          : the shared secret, HEX encoded
     * @param time         : a value that reflects a time
     * @param returnDigits : number of digits to return
     * @param crypto       : the crypto function to use
     * @return: a numeric String in base 10 that includes {@link truncationDigits} digits
     */
    public static String generateTOTP(String key, String time, String returnDigits, String crypto) {
        int codeDigits = Integer.decode(returnDigits).intValue();
        String result = null;
        // Using the counter
        // First 8 bytes are for the movingFactor
        // Compliant with base RFC 4226 (HOTP)
        while (time.length() < 16)
            time = "0" + time;
        // Get the HEX in a Byte[]
        byte[] msg = hexStr2Bytes(time);
        byte[] k = hexStr2Bytes(key);

        byte[] hash = hmac_sha(crypto, k, msg);
        // put selected bytes into result int
        int offset = hash[hash.length - 1] & 0xf;
        int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
        int otp = binary % DIGITS_POWER[codeDigits];
        result = Integer.toString(otp);
        while (result.length() < codeDigits) {
            result = "0" + result;
        }
        return result;
    }

    public static void main(String[] args) {
        // Seed for HMAC-SHA1 - 20 bytes
        //String seed = "3132333435363738393031323334353637383930";
        // Seed for HMAC-SHA256 - 32 bytes
        String seed32 = "123456789012345678901234567890z" + "313233343536373839303132";
        // Seed for HMAC-SHA512 - 64 bytes
        //String seed64 = "3132333435363738393031323334353637383930" + "3132333435363738393031323334353637383930"
                //+ "3132333435363738393031323334353637383930" + "31323334";

        long T0 = 0;
        long X = 30; // 生成的口令码有效期30S，即30S内生成的码都是一样的
        //long testTime[] = { 59L, 1111111109L, 1111111111L, 1234567890L,	2000000000L, 20000000000L };
        //long testTime[] = { 59L,60L };

        //long long1 = System.currentTimeMillis();
        //long long2 = new Date().getTime();

        //long long1 = getTimeByMinute1(-5);
        //long long2 = getTimeByMinute1(5);

        //说明：手机时间与北京时间允许误差2分钟，口令码有效时间2分钟
        //如：当前时间12:50分，手机误差区间12:48-12:52，以最大误差算口令码存在满2分钟区间为12:50-12:54
        //PC端总范围：12:48-12:54，即当前时间前2分钟到当前时间后4分钟，共6分钟
        int maxNum = (int) (360 / X);
        long longTime[] = new long[maxNum + 1];
        int nn = -119;//当前时间的前2分钟

        for (int mm = 0; mm < maxNum + 1; mm++) {
            longTime[mm] = getTimeBySecond(nn);
            nn = (int) (nn + X);
        }
        long testTime[] = longTime;
        String steps = "0";
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        try {
            System.out.println("+-------------+---------------------+" + "------------------+--------+--------+");
            System.out.println("| Time(sec)   | Time (UTC format)   " + "| Value of T(Hex)  |  TOTP  |  Mode  |");
            System.out.println("+-------------+---------------------+" + "------------------+--------+--------+");

            for (int i = 0; i < testTime.length; i++) {
                long T = (testTime[i] - T0) / X;
                steps = Long.toHexString(T).toUpperCase();
                while (steps.length() < 16)
                    steps = "0" + steps;
                String fmtTime = String.format("%1$-11s", testTime[i]);
                String utcTime = df.format(new Date(testTime[i] * 1000));
//                System.out.print("| " + fmtTime + " | " + utcTime + " | " + steps + " |");
//                System.out.println(generateTOTP(seed, steps, "6", "HmacSHA1") + "| SHA1 |");
                System.out.println("| " + fmtTime + " | " + utcTime + " | " + steps + " | " + generateTOTP256(seed32, steps, "6") + " | SHA256 |");
                //System.out.print("| " + fmtTime + " | " + utcTime + " | " + steps + " |");
                //System.out.println(generateTOTP(seed64, steps, "6", "HmacSHA512") + "| SHA512 |");
                System.out.println("+-------------+---------------------+" + "------------------+--------+--------+");
            }
        } catch (final Exception e) {
            System.out.println("Error : " + e);
        }
    }
    
    public static String createTOTP(String strUserId, String strUdid) {
    	String strTotp = "";
    	try {
    		if (strUdid.equals("")){
    			strTotp = "false您的设备未注册，禁止使用系统！";
    		}else{
    			strTotp = ",";
		        // Seed for HMAC-SHA256 - 32 bytes
    			System.out.println("==="+strUserId +"suncn"+ strUdid);
	    		String seed32 = stringToAscii(strUserId +"suncn"+ strUdid); // 用户名+设备号
	    		System.out.println("seed32==="+seed32);
		
		        long T0 = 0;
		        long X = 120; // 生成的口令码有效期30S，即30S内生成的码都是一样的
		
		        //说明：手机时间与北京时间允许误差5分钟，口令码有效时间2分钟
		        //如：当前时间12:50分，手机误差区间12:45-12:55，以最大误差算口令码存在满2分钟区间为12:50-12:57
		        //PC端总范围：12:45-12:57，即当前时间前5分钟到当前时间后7分钟，共12分钟
		        int maxNum = (int) (720 / X);
		        long longTime[] = new long[maxNum + 1];
		        int nn = -299;
		
		        for (int mm = 0; mm < maxNum + 1; mm++) {
		            longTime[mm] = getTimeBySecond(nn);
		            nn = (int) (nn + X);
		        }
		        long testTime[] = longTime;
		        String steps = "0";
		        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		        df.setTimeZone(TimeZone.getTimeZone("UTC"));
	        
	            for (int i = 0; i < testTime.length; i++) {
	                long T = (testTime[i] - T0) / X;
	                steps = Long.toHexString(T).toUpperCase();
	                while (steps.length() < 16)
	                    steps = "0" + steps;
	                //String fmtTime = String.format("%1$-11s", testTime[i]);
	                //String utcTime = df.format(new Date(testTime[i] * 1000));
	                strTotp += generateTOTP256(seed32, steps, "6")+",";//生成口令
	            }
    		}
    		
        } catch (Exception e) {
        	e.printStackTrace();
        }
    	return strTotp;
    }

    public static String getTimeByMinute(int minute) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, minute);
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
    }

    public static long getTimeByMinute1(int minute) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, minute);
        String tmp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
        System.out.println("tmp===" + tmp);
        return dateToLong(calendar.getTime());
    }

    public static long getTimeBySecond(int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, second);
        String tmp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
//        System.out.println("tmp===" + tmp);
        return dateToLong(calendar.getTime());
    }

    // date类型转换为long类型
    // date要转换的date类型的时间
    public static long dateToLong(Date date) {
        return date.getTime() / 1000;
    }
    
    //字符类型转换为char
    public static String stringToAscii(String value) {
        StringBuilder sbu = new StringBuilder();
        char[] chars = value.toCharArray();
        for (char aChar : chars) {
            sbu.append((int) aChar);
        }
        String s = sbu.toString();
        if (s.length() % 2 == 1) {
            s = s + "0";
        }
        return s;
    }

}
